﻿using System;

using Microsoft.VisualStudio.Package;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.WowAddonStudio.Lua.LanguageService.Intellisense;
using Microsoft.WowAddonStudio.Lua.Model;

namespace Microsoft.WowAddonStudio.Lua.LanguageService.Model
{
    public class LuaDeclarations : Declarations
    {
        private readonly LuaLanguageService languageService;
        private readonly Declaration[] declarations;
        private TextSpan commitSpan;

        public LuaDeclarations(LuaLanguageService languageService, Declaration[] declarations)
        {
            if (languageService == null)
                throw new ArgumentNullException("languageService");
            if (declarations == null)
                throw new ArgumentNullException("declarations");

            this.languageService = languageService;
            this.declarations = declarations;
        }

        /// <summary>
        /// Gets the count.
        /// </summary>
        /// <returns></returns>
        public override int GetCount()
        {
            return declarations.Length;
        }

        /// <summary>
        /// Gets the display text.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <returns></returns>
        public override string GetDisplayText(int index)
        {
            return declarations[index].GetDisplayText();
        }

        /// <summary>
        /// Gets the name.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <returns></returns>
        public override string GetName(int index)
        {
            return declarations[index].GetName();
        }

        /// <summary>
        /// Gets the description.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <returns></returns>
        public override string GetDescription(int index)
        {
            return declarations[index].GetDescription();
        }

        /// <summary>
        /// Gets the glyph.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <returns></returns>
        public override int GetGlyph(int index)
        {
            return GetGlyphImageIndex(declarations[index]);
        }

        // This method is called to get the string to commit to the source buffer.
        // Note that the initial extent is only what the user has typed so far.
        /// <summary>
        /// Called to commit the specified item to the source file.
        /// </summary>
        /// <param name="textView">[in] An <see cref="T:Microsoft.VisualStudio.TextManager.Interop.IVsTextView"/> object representing the view that displays the source file.</param>
        /// <param name="textSoFar">[in] A string containing the text that has been typed by the user.</param>
        /// <param name="commitCharacter">[in] The character used to commit the text.</param>
        /// <param name="index">[in] The index of the item to commit to the source file.</param>
        /// <param name="initialExtent">[in, out] A <see cref="T:Microsoft.VisualStudio.TextManager.Interop.TextSpan"/> object specifying the text typed so far. Returns the span of the committed text.</param>
        /// <returns>
        /// If successful, returns a string containing the text to commit to the source file; otherwise, returns null.
        /// </returns>
        public override string OnCommit(IVsTextView textView,
                                        string textSoFar,
                                        char commitCharacter,
                                        int index,
                                        ref TextSpan initialExtent)
        {
            // If character is a commit character, but no item is selected, just commit the text
            // typed so far and the commit character to get the completion list to be dismissed
            if (index == -1)
                return textSoFar + commitCharacter;

            // Do not commit the selected declaration when the user presses space
            if (commitCharacter == ' ')
                return textSoFar;

            // We intercept this call only to get the initial extent
            // of what was committed to the source buffer.
            commitSpan = initialExtent;

            return base.OnCommit(textView,
                                 textSoFar,
                                 commitCharacter,
                                 index,
                                 ref initialExtent);
        }

        // This method is called after the string has been committed to the source buffer.
        /// <summary>
        /// Called after the declaration has been committed to the source file. When implemented in a derived class, it provides a completion character which may itself be a trigger for another round of IntelliSense.
        /// </summary>
        /// <param name="textView">[in] An <see cref="T:Microsoft.VisualStudio.TextManager.Interop.IVsTextView"/> object representing the view that displays the source file.</param>
        /// <param name="committedText">[in] A string containing the text that was inserted as part of the completion process.</param>
        /// <param name="commitCharacter">[in] The character that was used to commit the text to the source file.</param>
        /// <param name="index">[in] The index of the item that was committed to the source file.</param>
        /// <returns>
        /// Returns a character to be inserted after the committed text. If nothing is to be inserted, returns 0.
        /// </returns>
        public override char OnAutoComplete(IVsTextView textView,
                                            string committedText,
                                            char commitCharacter,
                                            int index)
        {
        // Do not replace when committed with \13 (enter)
        if (commitCharacter == 13)
            return '\0';

            Declaration declaration = index != -1 ? declarations[index] : null;
            if (declaration != null)
            {
                if (declaration is Snippet)
                {
                    Source source = languageService.GetSource(textView);
                    if (source != null)
                    {
                        ExpansionProvider expansionProvider = source.GetExpansionProvider();
                        if (expansionProvider != null)
                        {
                            string title;
                            string path;
                            int commitLength = commitSpan.iEndIndex - commitSpan.iStartIndex;
                            if (commitLength < committedText.Length)
                            {
                                // Replace everything that was inserted
                                // so calculate the span of the full
                                // insertion, taking into account what
                                // was inserted when the commitSpan
                                // was obtained in the first place.
                                commitSpan.iEndIndex += (committedText.Length - commitLength);
                            }

                            if (expansionProvider.FindExpansionByShortcut(textView,
                                                                          committedText,
                                                                          commitSpan,
                                                                          true,
                                                                          out title,
                                                                          out path) == 0)
                            {
                                expansionProvider.InsertNamedExpansion(textView,
                                                                       title,
                                                                       path,
                                                                       commitSpan,
                                                                       false);
                            }
                        }
                    }
                }
            }
            return '\0';
        }

        private static int GetGlyphImageIndex(Declaration declaration)
        {
            int glyph = declaration.IsLocal ? (int)GlyphImageIndex.AccessPrivate : (int)GlyphImageIndex.AccessPublic;

            if (declaration is Variable)
                glyph += (int)GlyphImageIndex.Variable;
            if (declaration is Parameter)
                glyph += (int)GlyphImageIndex.Variable;
            if (declaration is Function)
                glyph += (int)GlyphImageIndex.Method;
            if (declaration is Table)
                glyph += (int)GlyphImageIndex.Class;

            if (declaration is Keyword)
                glyph = (int)GlyphImageIndex.Keyword;
            if (declaration is Snippet)
                glyph = (int)GlyphImageIndex.Snippet;

            return glyph;
        }
    }
}